/**
 * ImageUtil.java   2012-10-06
 *
 */
package com._21cn.framework.utils.image;

import com._21cn.framework.utils.StringUtil;
import org.im4java.core.ConvertCmd;
import org.im4java.core.IM4JavaException;
import org.im4java.core.IMOperation;
import org.im4java.core.InfoException;
import org.im4java.process.Pipe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * <p>Title: </p>
 * <p>Description:这是一个专门处理图片的类,缩略,旋转,水印 </p>
 *
 * @version 1.0
 */

public class IMImageUtil
{
	private static final Logger log = LoggerFactory.getLogger(IMImageUtil.class);

	private boolean useGM = true;
	
	private int maxWidth = 1280;
	private int maxHeight = 1280;
	
	public int getMaxWidth() {
    	return maxWidth;
    }

	public void setMaxWidth( int maxWidth ) {
    	this.maxWidth = maxWidth;
    }

	public int getMaxHeight() {
    	return maxHeight;
    }

	public void setMaxHeight( int maxHeight ) {
    	this.maxHeight = maxHeight;
    }

	public boolean isUseGM() {
    	return useGM;
    }

	public void setUseGM( boolean useGM ) {
    	this.useGM = useGM;
    }

	public long resize(String srcPath, String destPath, Integer newWidth, Integer newHeight, String mode  ) {
		return resize( srcPath, destPath, newWidth, newHeight, mode, null );
	}

	/**
	 * 缩小图片,产生缩略图
	 * @param srcPath 源图片路径
	 * @param destPath 目标文件名，全路径；可以为"格式:-"，表示按指定格式输出到标准输出，然后通过管道输出到目标输出流out，需要传入out参数
	 * @param out 目标输出流
	 * @param newWidth 缩小后的宽度，可以是像素，百分比，（或为null，表示不限定）
	 * @param newHeight 缩小后的高度，可以是像素，百分比，（或为null，表示不限定）
	 * @param mode: 
	 * 		>或null， 不超过指定宽高，其中一个可以为null
	 * 		^ 按比列缩放，实际宽高取决于指定的最小值；其中一个可以为null
	 * 		! 强制压缩为指定大小，会变形
	 * 		@ 为指定尺寸相当面积的最大尺寸，按比列缩放
	 * @return 如果成功返回目标文件的大小,否则返回0
	 */
	public long resize(String srcPath, String destPath, Integer newWidth, Integer newHeight, String mode, OutputStream out ) {
		try {
			IMOperation op = new IMOperation();
            op.colorspace("RGB");
		    op.size( maxWidth, maxHeight );
		    op.addImage(srcPath);
		    op.resize( newWidth, newHeight, mode );
		    op.p_profile( "*" );
		    return doConvert( op, destPath, out );
		} catch (Exception e) {
			e.printStackTrace();
			return 0L;
		}
	}

	public long thumbnail(String srcPath, String destPath, Integer newWidth, Integer newHeight, String mode ) {
		return thumbnail( srcPath, destPath, newWidth, newHeight, mode, null);
	}

	/**
	 * 等同于resize，按文档的说法，会更快一些，待测试
	 * @param srcPath
	 * @param destPath 目标文件名，全路径；可以为"格式:-"，表示按指定格式输出到标准输出，然后通过管道输出到目标输出流out，需要传入out参数
	 * @param out 目标输出流
	 * @param newWidth
	 * @param newHeight
	 * @param mode
	 * @return
	 */
	public long thumbnail(String srcPath, String destPath, Integer newWidth, Integer newHeight, String mode, OutputStream out ) {
		try {
			IMOperation op = new IMOperation();
            op.colorspace("RGB");
		    op.size( maxWidth, maxHeight );
		    op.addImage(srcPath);
		    op.thumbnail( newWidth, newHeight, mode );
		    op.p_profile( "*" );
		    return doConvert( op, destPath, out );
		} catch (Exception e) {
			e.printStackTrace();
			return 0L;
		}
	}
	
	public long scale(String srcPath, String destPath, Integer newWidth, Integer newHeight  ) {
		return scale( srcPath, destPath, newWidth, newHeight, null);
	}

	/**
	 * 简单快速的压缩算法，在4种(sample,thumbnail,resize,scale)算法中最快
	 * @param srcPath
	 * @param destPath 目标文件名，全路径；可以为"格式:-"，表示按指定格式输出到标准输出，然后通过管道输出到目标输出流out，需要传入out参数
	 * @param out 目标输出流
	 * @param newWidth
	 * @param newHeight
	 * @return
	 */
	public long scale(String srcPath, String destPath, Integer newWidth, Integer newHeight, OutputStream out ) {
		try {
			IMOperation op = new IMOperation();
            op.colorspace("RGB");
			op.p_profile("*");
			op.size( maxWidth, maxHeight);
		    op.addImage(srcPath);
			op.scale( newWidth, newHeight );
		    return doConvert( op, destPath, out );
		} catch (Exception e) {
			e.printStackTrace();
			return 0L;
		}
	}
	
	public long sample(String srcPath, String destPath, Integer newWidth, Integer newHeight ) {
		return sample( srcPath, destPath, newWidth, newHeight, null );
	}

	/**
	 * 通过取样的方式缩放图片，质量相对差一些
	 * @param srcPath
	 * @param destPath 目标文件名，全路径；可以为"格式:-"，表示按指定格式输出到标准输出，然后通过管道输出到目标输出流out，需要传入out参数
	 * @param out 目标输出流
	 * @param newWidth
	 * @param newHeight
	 * @return
	 */
	public long sample(String srcPath, String destPath, Integer newWidth, Integer newHeight, OutputStream out ) {
		try {
			IMOperation op = new IMOperation();
            op.colorspace("RGB");
		    op.size( maxWidth, maxHeight );
		    op.addImage(srcPath);
		    op.sample( newWidth, newHeight );
		    op.p_profile( "*" );
		    return doConvert( op, destPath, out );
		} catch (Exception e) {
			e.printStackTrace();
			return 0L;
		}
	}

	public long rotate( String srcPath,String destPath, Double degrees, String backgroudColor, Character mode ){
		return rotate( srcPath, destPath, degrees, backgroudColor,  mode, null );
	}
	
	/**
	 * 旋转图片
	 * @param srcPath 源图片路径
	 * @param destPath 目标文件名，全路径；可以为"格式:-"，表示按指定格式输出到标准输出，然后通过管道输出到目标输出流out，需要传入out参数
	 * @param out 目标输出流
	 * @param degrees 旋转角度
	 * @param backgroudColor 背景颜色：
	 * name               (named color)
     * #RGB               (hex numbers, 4 bits each)
     * #RRGGBB            (8 bits each)
     * #RRRGGGBBB         (12 bits each)
     * #RRRRGGGGBBBB      (16 bits each)
     * #RGBA              (4 bits each)
     * #RRGGBBAA          (8 bits each)
     * #RRRGGGBBBAAA      (12 bits each)
     * #RRRRGGGGBBBBAAAA  (16 bits each)
     * rgb(r,g,b)         (r,g,b are decimal numbers)
     * rgba(r,g,b,a)      (r,g,b,a are decimal numbers)
	 * @param mode ：null,>,< :
	 *        Use > to rotate the image only if its width exceeds the height. < rotates the image only if its width is less than the height. For example, if you specify -rotate "-90>" and the image size is 480x640, the image is not rotated. However, if the image is 640x480, it is rotated by -90 degrees. If you use > or <, enclose it in quotation marks to prevent it from being misinterpreted as a file redirection.
	 * @return 如果成功返回目标文件的大小,否则返回0
	 */
	public long rotate( String srcPath,String destPath, Double degrees, String backgroudColor, Character mode, OutputStream out  ){
		try {
			IMOperation op = new IMOperation();
            op.colorspace("RGB");
		    //op.size( maxWidth, maxHeight );
		    op.addImage(srcPath);
		    if( !StringUtil.isEmpty( backgroudColor ) )
		    	op.background( backgroudColor );
		    op.rotate( degrees, mode );
		    
		    return doConvert( op, destPath, out );
		} catch (Exception e) {
			e.printStackTrace();
			return 0L;
		}
	}

	
	public long crop( String  srcPath, String destPath, Integer x, Integer y, Integer width, Integer height, String mode, String gravity ){
		return crop( srcPath, destPath, x, y, width, height, mode, gravity, null );
	}

	/**
	 * 切割图片
	 * @param srcPath 源图片文件名
	 * @param destPath 目标文件名，全路径；可以为"格式:-"，表示按指定格式输出到标准输出，然后通过管道输出到目标输出流out，需要传入out参数
	 * @param out 目标输出流
	 * @param x 起点x坐标，单位像素
	 * @param y 起点y坐标，单位像素
	 * @param width 目标宽度，单位像素
	 * @param height 目标高度，单位像素
	 * @param mode 同resize的mode
	 * @param gravity 原始方向：NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast.
	 * @return 如果成功返回目标文件的大小,否则返回0
	 */
	public long crop( String  srcPath, String destPath, Integer x, Integer y, Integer width, Integer height, String mode, String gravity, OutputStream out ){
		try {
			IMOperation op = new IMOperation();
            op.colorspace("RGB");
		    op.size( maxWidth, maxHeight );
		    op.addImage(srcPath);
		    if( !StringUtil.isEmpty( gravity ) )
		    	op.gravity( gravity );
		    op.crop( width, height, x, y, mode );
		    return doConvert( op, destPath, out );
		} catch (Exception e) {
			e.printStackTrace();
			return 0L;
		}
	}

	
	/**
	 * 切割图片
	 * @param srcPath 源图片文件名
	 * @param destPath 目标文件名，全路径
	 * @param x 起点x坐标，单位像素
	 * @param y 起点y坐标，单位像素
	 * @param w 目标宽度，单位像素
	 * @param h 目标高度，单位像素
	 * @return 如果成功返回目标文件的大小,否则返回0
	 */
	public long cropImage( String  srcPath, String destPath, int x, int y, int w, int h ){
		int sw = 0;
		int sh = 0;
        try {
        	IMImageInfo info = new IMImageInfo(srcPath , true, true);
	        sw = info.getImageWidth();
			sh = info.getImageHeight();
        }
        catch( InfoException e ) {
	        e.printStackTrace();
        }

        x = ( x<0 ? ( sw + x ) : ( x>=sw ? 0 : x ) );
        y = ( y<0 ? ( sh + y ) : ( y>=sh ? 0 : y ) );
        w = ( (x + w) >= sw ? ( sw>x ? (sw - x) : sw ) : w );
        h = ( (y + h) >= sh ? ( sh>y ? (sh - y) : sh ) : h );
        //w = w<=0 ? sw: w;
        //h = h<=0 ? sh: h;
        return crop( srcPath, destPath, x, y, w, h, null,null );
	}

	/**
	 * 切割出符合要求的缩略图，并保存到指定文件中
	 * @param srcPath 源图片文件名
	 * @param minW 目标宽度，单位像素
	 * @param minH 目标高度，单位像素
	 * @param destPath 目标文件名，全路径
	 * @return 如果成功返回目标文件的大小,否则返回0
	 */
	public long cropMinImage( String srcPath, int minW, int minH, String destPath ){
		return cropMinImage( srcPath, minW, minH, destPath, null );
	}
	/**
	 * 切割出符合要求的缩略图，并保存到指定文件中
	 * @param srcPath 源图片文件名
	 * @param minW 目标宽度，单位像素
	 * @param minH 目标高度，单位像素
	 * @param destPath 目标文件名，全路径；可以为"格式:-"，表示按指定格式输出到标准输出，然后通过管道输出到目标输出流out，需要传入out参数
	 * @param out 目标输出流
	 * @return 如果成功返回目标文件的大小,否则返回0
	 */
	public long cropMinImage( String srcPath, int minW, int minH, String destPath, OutputStream out ){
		int sw = 0;
		int sh = 0;
        try {
        	IMImageInfo info = new IMImageInfo(srcPath , true, true);
	        sw = info.getImageWidth();
			sh = info.getImageHeight();
			log.debug( "format:" + info.getImageFormat()+",class:" + info.getImageClass() );
			
	        float r = ((float)minW)*sh/minH/sw;
			int minWzoom = minW;
			int minHzoom = minH;
			if( r < 1 ){
				minWzoom = (int)(minW*1.1);
				minHzoom = (int)(minH*1.1);
			}
			int[] newWH = ImageUtil.getRealWidthHeightForMin( sw, sh, minWzoom, minHzoom );
			
			int x = ( newWH[0] - minW ) / 2;
	        int y = (int)(( newWH[1] - minH ) / (4*r));
	        int targetW = (x>=0? minW : newWH[0]);
	        int targetH = (y>=0? minH : newWH[1]);
	        x = (x>=0? x : 0 );
	        y = (y>=0? y : 0 );
	        log.debug("x:"+x+",y:"+y+",targetW:"+targetW+",targetH:"+targetH +",r:"+r  );
	        
	        IMOperation op = new IMOperation();
            op.colorspace("RGB");
			op.p_profile( "*" );
			op.size( maxWidth, maxHeight );
		    op.addImage(srcPath);
		    op.scale( newWH[0], newWH[1] );
		    op.gravity( "NorthWest");
			op.crop(targetW, targetH, x, y );
			return doConvert( op, destPath, out );
		} catch (Exception e) {
			e.printStackTrace();
		}
		return 0L;
	}
	
	public long append( String[] srcPaths, Integer srcWidth, Integer srcHeight, boolean horizontally, String destPath){
		return append( srcPaths, srcWidth, srcHeight, horizontally, destPath, null );
	}

	/**
	 * 拼接图片
	 * @param srcPaths 多张原图图片
	 * @param srcWidth 每个源图片的目标宽度，可以为null
	 * @param srcHeight 每个源图片的目标高度，可以为null
	 * @param horizontally true是水平方向拼接，false是垂直方向
	 * @param destPath 目标文件名，全路径；可以为"格式:-"，表示按指定格式输出到标准输出，然后通过管道输出到目标输出流out，需要传入out参数
	 * @param out 目标输出流
	 * @return
	 */
	public long append( String[] srcPaths, Integer srcWidth, Integer srcHeight, boolean horizontally, String destPath, OutputStream out ){
		try {
			IMOperation op = new IMOperation();
            op.colorspace("RGB");
		    for( String img: srcPaths ){
			    op.scale( srcWidth, srcHeight );
			    op.addImage( img );
		    }
		    if( horizontally )
		    	op.appendHorizontally();
		    else
		    	op.appendVertically();
		    op.p_profile( "*" );
		    return doConvert( op, destPath, out );
		} catch (Exception e) {
			e.printStackTrace();
			return 0L;
		}		
	}

	/**
	 * 在图片上画文字，图片或基本图形
	 * @param srcPath 源图片
	 * @param drawString: 
	 		 point           x,y
     		 line            x0,y0 x1,y1
		     rectangle       x0,y0 x1,y1
		     roundRectangle  x0,y0 x1,y1 wc,hc
		     arc             x0,y0 x1,y1 a0,a1
		     ellipse         x0,y0 rx,ry a0,a1
		     circle          x0,y0 x1,y1
		     polyline        x0,y0  ...  xn,yn
		     polygon         x0,y0  ...  xn,yn
		     Bezier          x0,y0  ...  xn,yn
		     path            path specification
		     image           operator x0,y0 w,h filename
		     text            x0,y0 string
	 * @param destPath 目标文件名，全路径；可以为"格式:-"，表示按指定格式输出到标准输出，然后通过管道输出到目标输出流out，需要传入out参数
	 * @param out 目标输出流
	 * @return
	 */
	public long draw( String srcPath, String drawString, String destPath, OutputStream out ){
		try {
			IMOperation op = new IMOperation();
            op.colorspace("RGB");
		    op.addImage( srcPath );
		    op.draw( drawString );
			op.p_profile( "*" );
			return doConvert( op, destPath, out );
		} catch (Exception e) {
			e.printStackTrace();
			return 0L;
		}		
	}

	public long doConvert( IMOperation op, String destPath, OutputStream out ) throws IOException, InterruptedException,
            IM4JavaException {
	    op.addImage(destPath);
	    ConvertCmd convert = new ConvertCmd( useGM );
	    //convert.setSearchPath( "C:/Program Files (x86)/GraphicsMagick-1.3.16-Q16/" );
	    if( destPath.endsWith( ":-" ) ){
	    	if( out != null )
	    		convert.setOutputConsumer( new Pipe(null,out) );
	    }
	    else{
	    	File p = new File(destPath).getParentFile();
	    	if( p != null )
	    		p.mkdirs();
	    }
	    convert.run(op);
	    log.debug( op.toString() );
	    if( !destPath.endsWith( ":-" ) )
	    	return new File(destPath).length();
	    return -1L;
    }


	public static void main( String[] args ) throws Exception{
		//String[] names = ImageIO.getReaderFormatNames();
		//for( int i=0; i<names.length; i++)
		//	System.out.println( names[i] );
		System.setProperty( "im4java.useGM", "true" );
		//int[] wh = getRealWidthHeight( 1024,768,2560,2560 );
		//System.out.println( wh[0] );
		//System.out.println( wh[1] );
		IMImageUtil im = new IMImageUtil();
		FileOutputStream out = new FileOutputStream( "d:/temp/rotated70.png");
		//im.rotate( "d:/temp/2.jpg", "jpeg:-", 70d, null,null, out );
		//im.cropMinImage( "d:/temp/2.jpg",  120, 100, "JPEG:-", out );
		//im.append( new String[]{"d:/temp/2.jpg", "d:/temp/1.jpg", "d:/temp/IM_2.jpg"}, null,400,  true, "png:-", out );
		im.draw( "d:/temp/ab.jpg", "image over 100,100 100,100 d:/temp/outfile.jpg", "JPEG:-", out );
		out.close();
		//rotate( "d:/temp/00111.jpg", "d:/temp/test/rotate45.jpg", 45 );
		//rotate( "d:/temp/00111.jpg", "d:/temp/test/rotate60.jpg", 60 );
		//rotate( "d:/temp/00111.jpg", "d:/temp/test/rotate90.jpg", 90 );
		//rotate( "d:/temp/00111.jpg", "d:/temp/test/rotate180.jpg", 180 );
		//rotate( "d:/temp/00111.jpg", "d:/temp/test/rotate270.jpg", 270 );
		//rotate( "d:/temp/8744316.jpg", "d:/temp/test/rotate180.jpg", 180 );
		
		//crop( "d:/temp/00111.jpg",  -200, 0, 200, 100, "d:/temp/crop1.jpg" );
		//waterMark( "d:/temp/00111.jpg", "d:/temp/biaoshi.gif" , -100, -40, 0.2f ,"d:/temp/test/watermark1.jpg" );
		//waterMark( "d:/temp/00111.jpg", "d:/temp/tellogo.gif" , -100, -40, 0.8f ,"d:/temp/test/watermark2.jpg" );
		
		//BufferedImage img = drawText( "d:/temp/crop1.jpg", "中文发财" , -100, 30 , "ff0000","ffffff", "宋体", 20, 1.0f );
		//img = ImageUtil.twistImage( img, false, 5, 3.0f );
		//ImageUtil.writeImage( img, "d:/temp/drawText2.png" );
		//drawText( "d:/temp/00111.jpg", "中文test by Jimmy" , -100, 40 , "ff0000","ffffff", "宋体", 24, 1.0f, "d:/temp/drawText1.png");
		//zoomMax( "d:/temp/8744316.jpg", "d:/temp/test/zoom1301.jpg", 300,500 );
		//System.out.println (zoomMax( "d:/temp/00111.jpg", "d:/temp/test/zoom1302.jpg", 200,0 ));
		//zoom( "d:/temp/00111-bmp.bmp", "d:/temp/test/zoom130-130bmp.jpg", 130,130 );
		//zoom( "d:/temp/00111-png.png", "d:/temp/test/zoom130-130png.png", 130,130 );
		//zoom( "d:/temp/8781705.jpg", "d:/temp/test/zoom130-130.jpg", 130,130 );
		//im.scale( "d:/temp/2.jpg", "d:/temp/jvm_scale_130.jpg", 200,200 );
		//zoom( "d:/temp/test/0011.jpg", "d:/temp/test/0011.jpg", 500,0 );
		//zoomMax( "d:/temp/2.jpg", "d:/temp/jvm_scale_200.jpg", 200,300 );
		//zoomMax( "d:/temp/0011.jpg", "d:/temp/test/zoommax1024.jpg", 1024,1024 );
		
		//long s = System.currentTimeMillis();
		//for( int i=1; i< 100; i++)
		//	drawText( "d:/temp/00111.jpg", "中文test by Jimmy" , -100, -40 , "ff0000","ffffff", "宋体", 14, 0.5f, "d:/temp/test/drawText1.png");
		//System.out.println( System.currentTimeMillis() - s );

		//ImageInfo info  = getImageInfo( "d:/temp/0011.jpg" );
		//getImageInfo( "d:/temp/8781705.jpg" );
		//System.out.print( (HashMap)info.getBaseExif() );
	}
	

}

